using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Reflection;

namespace ReactiveLang
{
    using Util;

    public interface IExpression<out T> : INode<IExpressionParent<T>>
    {
    }

    public interface IAssignableExpression<T> : IExpression<T>
    {
        void Assign(T value);
    }

    public static partial class Expression
    {
        public static readonly IExpression<Unit> Unit = Constant(default(Unit));

        #region Linq syntax

        internal static IExpression<TResult> Select<TSource, TResult>(this IExpression<TSource> source, Func<TSource, TResult> resultSelector)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(resultSelector != null, "resultSelector");
            return source.Unary(resultSelector);
        }
        internal static IExpression<TResult> SelectMany<TSource, TCollection, TResult>(this IExpression<TSource> source, Func<TSource, IExpression<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(collectionSelector != null, "collectionSelector");
            Contract.Requires<ArgumentNullException>(resultSelector != null, "resultSelector");
            return source.Apply(a => collectionSelector(a).Unary(b => resultSelector(a, b)));
        }

        #endregion

        #region Create

        class AnonymousExpression<T> : IExpression<T>
        {
            internal Func<IExpressionParent<T>, IDisposable> _activate;
            public IDisposable Activate(IExpressionParent<T> parent)
            {
                return _activate(parent);
            }
        }
        class AnonymousAssignableExpression<T> : AnonymousExpression<T>, IAssignableExpression<T>
        {
            internal Action<T> _assign;
            public void Assign(T value)
            {
                _assign(value);
            }
        }

        public static IExpression<T> CreateWithDisposable<T>(Func<IExpressionParent<T>, IDisposable> activate)
        {
            Contract.Requires<ArgumentNullException>(activate != null, "activate");
            Contract.Ensures(Contract.Result<IExpression<T>>() != null);

            return new AnonymousExpression<T> { _activate = activate };
        }
        public static IAssignableExpression<T> CreateWithDisposable<T>(Func<IExpressionParent<T>, IDisposable> activate, Action<T> assign)
        {
            Contract.Requires<ArgumentNullException>(activate != null, "activate");
            Contract.Requires<ArgumentNullException>(assign != null, "assign");
            Contract.Ensures(Contract.Result<IExpression<T>>() != null);

            return new AnonymousAssignableExpression<T> { _activate = activate, _assign = assign };
        }
        public static IExpression<T> Create<T>(Func<IExpressionParent<T>, Action> activate)
        {
            Contract.Requires<ArgumentNullException>(activate != null, "activate");
            Contract.Ensures(Contract.Result<IExpression<T>>() != null);
            return CreateWithDisposable<T>(p => Disposable.Create(activate(p)));
        }
        public static IAssignableExpression<T> Create<T>(Func<IExpressionParent<T>, Action> activate, Action<T> assign)
        {
            Contract.Requires<ArgumentNullException>(activate != null, "activate");
            Contract.Requires<ArgumentNullException>(assign != null, "assign");
            Contract.Ensures(Contract.Result<IExpression<T>>() != null);
            return CreateWithDisposable<T>(p => Disposable.Create(activate(p)), assign);
        } 

        #endregion

        #region Activate

        internal static IDisposable Activate<T>(this IExpression<T> expression, Action<T> onValue, Action<Exception> onException = null)
        {
            Contract.Requires<ArgumentNullException>(expression != null, "expression");
            Contract.Requires<ArgumentNullException>(onValue != null, "onValue");
            onException = onException ?? (ex => { throw  ex; });
            return expression.Activate(ExpressionParent.Create(onValue, onException));
        }
        internal static IDisposable ActivateDerived<T, T2>(this IExpression<T> expression, IExpressionParent<T2> grandParent, Action<T> onValue)
        {
            Contract.Requires(expression != null);
            Contract.Requires(grandParent != null);
            Contract.Requires(onValue != null);
            return expression.Activate(onValue, grandParent.OnException);
        }

        #endregion

        internal static IExpression<T> Memoize<T>(this IExpression<T> expression)
        {
            LatestNotification<T> latest = null;
            int count = 0;

            return Create<T>(p =>
            {
                if (count == 0)
                    latest = new LatestNotification<T>(expression);

                var handle = latest.Activate(p);
                count++;

                return delegate
                {
                    handle.Dispose();
                    count--;
                    if (count == 0)
                    {
                        // last activation
                        latest.Dispose();
                        latest = null;
                    }
                };
            });
        }
        internal static IExpression<T> Distinct<T>(this IExpression<T> expression)
        {
            Contract.Requires<ArgumentNullException>(expression != null, "expression");
            return CreateWithDisposable<T>(p =>
            {
                var latest = expression.Latest(p, true);
                latest.Activate(p);
                return latest;
            });
        }
        internal static IExpression<T> HideIdentity<T>(this IExpression<T> expression)
        {
            Contract.Requires<ArgumentNullException>(expression != null, "expression");
            return CreateWithDisposable<T>(expression.Activate);
        }
        internal static IAssignableExpression<T> MakeWritable<T>(this IExpression<T> expression, Action<T> assign)
        {
            Contract.Requires<ArgumentNullException>(expression != null, "expression");
            Contract.Requires<ArgumentNullException>(assign != null, "assign");
            return CreateWithDisposable<T>(expression.Activate, assign);
        }

        public static IExpression<Unit> AsUnit<T>(this IExpression<T> source)
        {
            Contract.Requires(source != null);
            return source.Unary(_ => default(Unit));
        }
        public static IExpression<Unit> WithInitial(this IExpression<Unit> source)
        {
            Contract.Requires(source != null);
            return CreateWithDisposable<Unit>(p =>
            {
                p.OnValue(new Unit());
                return source.Activate(p);
            });
        }

        internal static IExpression<T> MakeWeak<T>(this IExpression<T> source)
        {
            Func<WeakReference<IExpressionParent<T>>, IDisposable> activate = p =>
            {
                var srcHandle = Disposable.Empty;
                srcHandle = source.ToNotifications().Activate(n =>
                {
                    if (p.IsAlive)
                        n.Accept(p.Target);
                    else
                        srcHandle.Dispose();
                });
                return srcHandle;
            };

            IExpression<T> result = null;
            result = Create<T>(p =>
            {
                var resultRef = result;
                var parentRef = p;
                var handle = activate(new WeakReference<IExpressionParent<T>>(p));
                return delegate
                {
                    handle.Dispose();
                    parentRef = null;
                    resultRef = null;
                };
            });

            var assignable = source as IAssignableExpression<T>;
            if (assignable != null)
                result = result.MakeWritable(assignable.Assign);

            return result;
        }
        internal static IAssignableExpression<T> MakeWeak<T>(this IAssignableExpression<T> source)
        {
            return (IAssignableExpression<T>) MakeWeak(source as IExpression<T>);
        }
    }
}